package com.headsen.v4;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
import com.google.common.collect.ImmutableMap;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

/**
 * 支持垃圾回收
 * <p>
 * 弱引用
 *
 * @author zxs
 * @version 1.0
 * @date 2022/7/30 9:32
 */
public class MyCacheV4<K, V> implements Cache<K, V> {
    /**
     * 数据结构
     */
    private Map<K, Entry<K, V>> data;
    private Entry<K, V> head;
    private Entry<K, V> tail;

    /**
     * 配置数据。0不失效
     */
    private int maxSize;
    private int expireTimeAfterRefresh;
    private int expireTimeAfterAccess;
    private ReferenceQueue<Object> queue;

    /**
     * @param initSize
     * @param maxSize
     * @param expireTimeAfterRefresh 毫秒
     * @param expireTimeAfterAccess
     */
    public MyCacheV4(int initSize, int maxSize, int expireTimeAfterRefresh, int expireTimeAfterAccess) {
        //参数校验
        this.maxSize = maxSize;
        this.expireTimeAfterRefresh = expireTimeAfterRefresh;
        this.expireTimeAfterAccess = expireTimeAfterAccess;

        this.data = new HashMap<>(initSize);
        queue = new ReferenceQueue<>();
        head = new Entry<>(null);
        tail = new Entry<>(null);
        head.next = tail;
        tail.prev = head;
    }

    /**
     * @param key
     * @return
     */
    @Override
    public V getIfPresent(Object key) {
        if (key == null) {
            return null;
        }
        removeWeakInvalid();
        Entry<K, V> entry = data.get(key);
        if (entry == null || entry.getValue() == null) {
            return null;
        }
        long l = System.currentTimeMillis();
        if (isExpire(l, entry)) {
            return null;
        }
        if (expireTimeAfterAccess != 0) {
            entry.expireTime = Math.max(System.currentTimeMillis() + expireTimeAfterAccess, entry.expireTime);
        }
        // 这里会加锁 当前节点移动到头部
        shiftFirst(head, entry);
        return entry.getValue();
    }

    @Override
    public V get(K key, Callable<? extends V> loader) throws ExecutionException {
        V v = getIfPresent(key);
        if (v != null) {
            return v;
        }
        try {
            v = loader.call();
        } catch (Exception e) {
            throw new ExecutionException(e);
        }
        if (v == null) {
            throw new NullPointerException();
        }
        put(key, v);
        return v;
    }

    @Override
    public ImmutableMap<K, V> getAllPresent(Iterable<?> keys) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void put(K key, V value) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("入参不合法");
        }
        synchronized (data) {
            removeWeakInvalid();
            Entry<K, V> vEntry = data.get(key);
            if (vEntry == null) {
                vEntry = new Entry<K, V>(value, queue, key);
                data.put(key, vEntry);
            }
            if (expireTimeAfterRefresh != 0) {
                vEntry.expireTime = Math.max(System.currentTimeMillis() + expireTimeAfterRefresh, vEntry.expireTime);
            }
            shiftFirst(head, vEntry);
            removeInvalid();
        }
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        if (m == null) {
            throw new IllegalArgumentException("入参不合法");
        }
        synchronized (data) {
            m.entrySet().forEach(x -> put(x.getKey(), x.getValue()));
        }
    }

    @Override
    public void invalidate(Object key) {
        if (key == null) {
            throw new IllegalArgumentException("入参不合法");
        }
        //这里直接删了
        synchronized (data) {
            Entry<K, V> vEntry = data.get(key);
            if (vEntry != null) {
                data.remove(key);
                remove(head, vEntry);
            }
        }
    }

    @Override
    public void invalidateAll(Iterable<?> keys) {
        if (keys == null) {
            throw new IllegalArgumentException("入参不合法");
        }
        synchronized (data) {
            keys.forEach(x -> invalidate(x));
        }
    }

    @Override
    public void invalidateAll() {
        synchronized (data) {
            data.clear();
            head.next = null;
        }
    }

    @Override
    public long size() {
        synchronized (data) {
            removeWeakInvalid();
            removeInvalid();
            return data.size();
        }
    }

    @Override
    public CacheStats stats() {
        throw new UnsupportedOperationException();
    }

    @Override
    public ConcurrentMap<K, V> asMap() {
        removeWeakInvalid();
        removeInvalid();
        ConcurrentHashMap<K, V> concurrentHashMap = new ConcurrentHashMap<>(data.size());
        synchronized (data) {
            data.entrySet().forEach(x -> concurrentHashMap.put(x.getKey(), x.getValue().getValue()));
        }
        return concurrentHashMap;
    }

    @Override
    public void cleanUp() {
        invalidateAll();
    }

    /**
     * 节点移动到头部
     *
     * @param head
     * @param entry
     */
    private void shiftFirst(Entry<K, V> head, Entry<K, V> entry) {
        synchronized (head) {
            if (head.next == entry) {
                return;
            }
            remove(head, entry);
            entry.prev = head;
            entry.next = head.next;
            head.next.prev = entry;
            head.next = entry;
        }
    }

    /**
     * 从链表移除节点
     *
     * @param head
     * @param entry
     */
    private void remove(Entry<K, V> head, Entry<K, V> entry) {
        //新节点
        if (entry.next == null) {
            return;
        }
        synchronized (head) {
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
        }
    }

    private void removeWeakInvalid() {
        synchronized (data) {
            Entry<K, V> vEntry = null;
            while ((vEntry = (Entry<K, V>) queue.poll()) != null) {
                synchronized (head) {
                    remove(head, vEntry);
                    data.remove(vEntry.key);
                }
            }
        }
    }

    /**
     * 清理失效的数据
     */
    private void removeInvalid() {
        synchronized (data) {
            long l = System.currentTimeMillis();
            synchronized (head) {
                Entry<K, V> vEntry = tail.prev;
                while (vEntry != null && vEntry != head && (
                        (isExpire(l, vEntry)) || (data.size() > maxSize)
                )) {
                    remove(head, vEntry);
                    data.remove(vEntry.key);
                    vEntry = vEntry.prev;
                }
            }
        }
    }

    private boolean isExpire(long l, Entry<K, V> vEntry) {
        return (vEntry.expireTime != 0 && vEntry.expireTime < l);
    }

    /**
     * 缓存元素封装
     *
     * @param <V>
     */
    private final class Entry<K, V> extends WeakReference<Object> {
        /**
         * 业务值
         */
        private K key;

        /**
         * 失效时间
         */
        private long expireTime;

        /**
         * 双向链表
         */
        private Entry<K, V> prev;
        private Entry<K, V> next;

        public Entry(V referent) {
            super(referent);
        }

        public Entry(V referent, ReferenceQueue<Object> q, K key) {
            super(referent, q);
            this.key = key;
        }

        public V getValue() {
            return (V) get();
        }
    }
}
